<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8"/>
  <title>动态粒子背景</title>
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
  <style>
    html,body{
      margin:0;
      height:100%;
      overflow:hidden;
      font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"PingFang SC",sans-serif;
      background:#0f1116; /* 深色底 */
    }
    #canvas{
      position:fixed;
      inset:0;
      width:100%;
      height:100%;
      z-index:-1;
    }
    /* 前景内容仅作示例 */
    .content{
      position:relative;
      color:#fff;
      text-align:center;
      padding-top:40vh;
      font-size:32px;
      text-shadow:0 0 8px rgba(255,255,255,.6);
      user-select:none;
    }
    .content small{
      display:block;
      margin-top:8px;
      font-size:16px;
      opacity:.7;
    }
  </style>
</head>
<body>

<canvas id="canvas"></canvas>

<div class="content">
  Dynamic Background
  <small>拖动鼠标 / 触摸屏幕 查看跟随效果</small>
</div>

<script>
(() => {
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');

  /* 1. 调整画布大小 */
  function resize() {
    canvas.width = innerWidth * devicePixelRatio;
    canvas.height = innerHeight * devicePixelRatio;
    ctx.scale(devicePixelRatio, devicePixelRatio);
  }
  window.addEventListener('resize', resize);
  resize();

  /* 2. 鼠标/触摸位置 */
  const pointer = { x: null, y: null };
  ['mousemove','touchstart','touchmove'].forEach(ev => {
    window.addEventListener(ev, e => {
      const t = e.touches ? e.touches[0] : e;
      pointer.x = t.clientX;
      pointer.y = t.clientY;
    });
  });
  window.addEventListener('mouseleave', () => (pointer.x = pointer.y = null));

  /* 3. 粒子类 */
  class Particle {
    constructor(x, y, isRandom = false) {
      this.x = x ?? Math.random() * innerWidth;
      this.y = y ?? Math.random() * innerHeight;
      this.size = Math.random() * 2 + 1;           // 半径
      this.speedX = (Math.random() - 0.5) * 0.8;
      this.speedY = (Math.random() - 0.5) * 0.8;
      this.color = `hsl(${Math.random()*360}, 100%, 70%)`;
      this.isRandom = isRandom;                    // 随机漂浮还是跟随鼠标
    }
    update() {
      this.x += this.speedX;
      this.y += this.speedY;

      /* 边界反弹 */
      if (this.x < 0 || this.x > innerWidth) this.speedX *= -1;
      if (this.y < 0 || this.y > innerHeight) this.speedY *= -1;
    }
    draw() {
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
      ctx.fillStyle = this.color;
      ctx.fill();
    }
  }

  /* 4. 创建粒子池 */
  const particles = [];
  const PARTICLE_COUNT = Math.min(120, Math.floor(innerWidth * innerHeight / 25000)); // 按分辨率自适应
  for (let i = 0; i < PARTICLE_COUNT; i++) {
    particles.push(new Particle(null, null, true));
  }

  /* 5. 鼠标跟随粒子 */
  let mouseParticles = [];
  function handlePointer() {
    if (pointer.x !== null && pointer.y !== null) {
      mouseParticles.push(new Particle(pointer.x, pointer.y));
    }
    /* 限制跟随粒子数量 */
    if (mouseParticles.length > 30) mouseParticles.shift();
    /* 淡出 */
    mouseParticles.forEach(p => {
      p.size *= 0.96;
      p.update();
    });
    mouseParticles = mouseParticles.filter(p => p.size > 0.5);
  }

  /* 6. 动画循环 */
  function animate() {
    ctx.clearRect(0, 0, innerWidth, innerHeight);

    /* 绘制连线（可选） */
    for (let i = 0; i < particles.length; i++) {
      for (let j = i + 1; j < particles.length; j++) {
        const dx = particles[i].x - particles[j].x;
        const dy = particles[i].y - particles[j].y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 100) {
          ctx.beginPath();
          ctx.strokeStyle = `rgba(255,255,255,${1 - dist / 100})`;
          ctx.lineWidth = 0.5;
          ctx.moveTo(particles[i].x, particles[i].y);
          ctx.lineTo(particles[j].x, particles[j].y);
          ctx.stroke();
        }
      }
    }

    /* 更新 & 绘制 */
    particles.forEach(p => { p.update(); p.draw(); });
    handlePointer();
    mouseParticles.forEach(p => p.draw());

    requestAnimationFrame(animate);
  }
  animate();
})();
</script>

</body>
</html>